﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	public class Class
	{
		public Class(Token originToken, string parent, string name, bool isStatic, bool isAbstract, string baseClass, IList<Field> fields, IList<Constructor> constructors, IList<Method> methods, IList<Property> properties, IList<Class> nestedClasses)
		{
			this.OriginToken = originToken;
			this.Name = name;
			Variable.Table.Register(name);
			this.FullName = parent + "." + name;
			this.IsStatic = isStatic;
			this.IsAbstract = isAbstract;
			this.BaseClass = baseClass;
			this.AvailableReferences = new Dictionary<string, object>();

			// TODO: check for duplicate names

			this.Fields = fields.ToArray();
			if (constructors.Count == 0 && !this.IsStatic)
			{
				constructors.Add(new Constructor(false, new MethodParam[0], new List<Executable>(), new Expression[0], originToken.RelativeFile));
			}
			this.Constructors = constructors.ToArray();
			this.Methods = methods.ToArray();
			this.Properties = properties.ToArray();
			this.NestedClasses = nestedClasses.ToArray();
		}

		public object GetMember(string name, bool includeBaseClasses)
		{
			foreach (Field f in this.Fields) { if (f.Name == name) return f; }
			foreach (Method m in this.Methods) { if (m.Name == name) return m; }
			foreach (Property p in this.Properties) { if (p.Name == name) return p; }
			foreach (Class c in this.NestedClasses) { if (c.Name == name) return c; }

			if (includeBaseClasses && this.BaseClassInstance != null)
			{
				return this.BaseClassInstance.GetMember(name, true);
			}
			return null;
		}

		public Dictionary<string, object> AvailableReferences { get; private set; }

		public int ID { get; set; }

		public Method StaticInitializer { get; private set; }

		public void AddInit(Method method)
		{
			this.StaticInitializer = method;
			List<Method> methods = new List<Method>(this.Methods);
			methods.Add(method);
			this.Methods = methods.ToArray();
		}

		public object[] GetMembers(bool includeFields, bool includeBaseclasses, bool includeStaticInitializer)
		{
			List<Object> members = new List<object>();

			if (includeBaseclasses && this.BaseClassInstance != null)
			{
				members.AddRange(this.BaseClassInstance.GetMembers(includeFields, true, includeStaticInitializer));
			}

			if (includeFields)
			{
				members.AddRange(this.Fields);
			}
			members.AddRange(this.NestedClasses);
			members.AddRange(this.Constructors);
			if (includeStaticInitializer)
			{
				members.AddRange(this.Methods);
			}
			else
			{
				foreach (Method m in this.Methods)
				{
					if (m.Name != null)
					{
						members.Add(m);
					}
				}
			}
			members.AddRange(this.Properties);

			return members.ToArray();
		}

		public object Parent { get; set; }
		public Class BaseClassInstance { get; set; }

		public string FullName { get; private set; }
		public bool IsAbstract { get; private set; }
		public bool IsStatic { get; private set; }
		public string Name { get; private set; }
		public int NameIndex { get { return Variable.Table.GetId(this.Name); } }
		public Token OriginToken { get; private set; }

		public string BaseClass { get; private set; }
		public Field[] Fields { get; private set; }
		public Constructor[] Constructors { get; private set; }
		public Method[] Methods { get; private set; }
		public Property[] Properties { get; private set; }
		public Class[] NestedClasses { get; private set; }

		private HashSet<string> members = new HashSet<string>();

		public static Class ParseClassIfPresent(Tokens tokens, string parent)
		{
			if (tokens.Peek() == "class")
			{
				return Class.Parse(tokens, parent);
			}

			return null;
		}

		public static Class Parse(Tokens tokens, string parent)
		{
			bool isStatic = tokens.PopIfPresent("static");
			bool isAbstract = tokens.PopIfPresent("abstract"); // TODO: enforce abstract in the parser
			Token origin = tokens.PopExpected("class");
			string name = tokens.PopIdentifier();
			string baseClass = null;

			if (tokens.PopIfPresent(":"))
			{
				baseClass = tokens.PopIdentifier();
				while (tokens.Peek() == ".")
				{
					tokens.PopExpected(".");
					baseClass += "." + tokens.PopIdentifier();
				}
			}

			List<Method> methods = new List<Method>();
			List<Property> properties = new List<Property>();
			List<Class> nestedClasses = new List<Class>();
			List<Constructor> constructors = new List<Constructor>();
			List<Field> fields = new List<Field>();
			tokens.PopExpected("{");

			while (tokens.Peek() != "}")
			{
				if (tokens.IsPresent("abstract", "static") || tokens.IsPresent("static", "abstract"))
					throw new ParserException("Something cannot be both static and abstract.", tokens.PeekToken());
				if (tokens.IsPresent("abstract", "constructor"))
					throw new ParserException("Constructors cannot be abstract.", tokens.PeekToken());
				if (tokens.IsPresent("abstract", "field"))
					throw new ParserException("Fields cannot be abstract.", tokens.PeekToken());
				
				if (tokens.IsPresent("field") || tokens.IsPresent("static", "field"))
				{
					Field field = Field.Parse(tokens);
					fields.Add(field);
				}
				else if (tokens.IsPresent("constructor") || tokens.IsPresent("static", "constructor"))
				{
					Constructor constructor = Constructor.Parse(tokens);
					constructors.Add(constructor);
				}
				else if (tokens.IsPresent("method") || tokens.IsPresent("static", "method") || tokens.IsPresent("abstract", "method"))
				{
					Method method = Method.Parse(tokens);
					methods.Add(method);
				}
				else if (tokens.IsPresent("property") || tokens.IsPresent("static", "property") || tokens.IsPresent("abstract", "property"))
				{
					Property property = Property.Parse(tokens);
					properties.Add(property);
				}
				else if (
					tokens.IsPresent("class") ||
					tokens.IsPresent("abstract", "class") ||
					tokens.IsPresent("static", "class"))
				{
					Class nestedClass = Class.Parse(tokens, parent);
					nestedClasses.Add(nestedClass);
				}
				else
				{
					throw ParserException.UnexpectedToken(tokens, tokens.PopInstance());
				}
			}

			tokens.PopExpected("}");

			int staticConstructorCount = 0;
			int normalConstructorCount = 0;
			foreach (Constructor constructor in constructors)
			{
				if (constructor.IsStatic)
				{
					++staticConstructorCount;
				}
				else
				{
					++normalConstructorCount;
				}
			}
			if (normalConstructorCount > 1)
			{
				// TODO: throw error: too many constructors
			}
			else if (staticConstructorCount > 1)
			{
				// TODO: throw error: too many static constructors
			}

			return new Class(origin, parent, name, isStatic, isAbstract, baseClass, fields, constructors, methods, properties, nestedClasses);
		}

		public override string ToString()
		{
			return "<Class> " + this.FullName ?? "[name not defined]";
		}
	}
}
